Bloc 範例 => GITHUB Bloc
Cubit 範例 => GITHUB Cubit
我們來看這張圖,簡單來說,他就是 UI 觸發一個事件,傳到了 bloc 後,處理邏輯,處理好後,丟出一個狀態。當 UI 接收到對應的狀態,給予對應的 UI。
Bloc 這個套件有兩種寫法。
這邊一般會先用一個 IPostEvent 然後後面的繼承他這樣子。前面加上一個 I 是因為這樣我比較好知道他是 abstract class。
Equatable 在前幾 Day 7 有講過。
abstract class IPostState {}
class PostLoading extends IPostState {}
class PostSuccess extends IPostState {
  final List<PostModel> postList;
  PostSuccess( this.postList);
}
PostCubit 丟了一個建構子,是一個 IPostRepository,用來請求資料的。super 是用來設定初始值的。(這邊預設的狀態是在 PostLoading)。emit 代表,輸出狀態。預設一剛開始就是 PostLoading 因此,在 fetchData、sort 結束後,狀態將會是 PostSuccess。enum SortState { userId, id, title, body }
class PostCubit extends Cubit<IPostState> {
  IPostRepository _repository;
  List<PostModel> postList = [];
  PostCubit(IPostRepository repository)
      : _repository = repository,
        super(PostLoading());
  Future<void> fetchData() async {
    postList = await _repository.fetchData();
    sort(SortState.id);
  }
  void sort(SortState sortBy) {
    switch (sortBy) {
      case SortState.userId:
        postList.sort((a, b) => a.userId.compareTo(b.userId));
        break;
      case SortState.id:
        postList.sort((a, b) => a.id.compareTo(b.id));
        break;
      case SortState.title:
        postList.sort((a, b) => a.title.compareTo(b.title));
        break;
      case SortState.body:
        postList.sort((a, b) => a.body.compareTo(b.body));
        break;
    }
    emit(PostSuccess([...postList]));
  }
}
Cubit 的初始化和後面會講到的 Bloc 一樣,和 Provider 類似。MultiBlocProvider 元件是一個初始化的共享裝置元件。
這邊我們在 MaterialApp 外面建立了一個 MultiBlocProvider,代表只要在 MaterialApp 裡面都可以取到 PostCubit 的資料。
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<PostCubit>(
          create: (BuildContext context) => PostCubit(
            PostRepository(PostService()),
          ),
        ),
      ],
      child: MaterialApp(
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Cubit Sample'),
      ),
    );
  }
}
Cubit 的觸發事件不會向 bloc 有一個專門的 Event,而是直接呼叫 PostCubit 裡面的 sort() 方法。
context.read<PostCubit>().sort(value);
Cubit 的 BlocBuilder 和 Bloc 一樣
BlocBuilder<PostCubit, IPostState>(
    builder: (_, state) {
      // 如果種態勢 PostSuccess 的話
      if (state is PostSuccess) {
        return Widget;
      }
      // else{} //預設值。
      return Center(child: CircularProgressIndicator());
    },
  );
這邊引用 flutter_bloc 文件範例
BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)
// or
// 你不想要共享資料的話,可以放在這。
BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)
我會這樣說,BlocBuilder 只是用來回應不同狀態下對應的 widgets,BlocListener 則是用來在不同狀態 "do things "。比方說跳頁、showSnackBar 等等...,你可以考範例 showsnackbar、Navigator
如果狀態是 PostSuccess,那麼我就回傳一個 ListView.builder,其他的則是回傳一個 CircularProgressIndicator。
class _MyListView extends StatelessWidget {
  const _MyListView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PostCubit, IPostState>(
      builder: (_, state) {
        if (state is PostSuccess) {
          return ListView.builder(
            itemCount: state.postList.length,
            itemBuilder: (context, index) {
              PostModel item = state.postList[index];
              return Container(
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(16)),
                      color: Colors.white,
                      border: Border.all(color: Colors.blueAccent, width: 2.0)),
                  margin: EdgeInsets.all(8),
                  padding: EdgeInsets.all(8),
                  child: RichText(
                    text: TextSpan(
                      style: DefaultTextStyle.of(context).style,
                      children: <TextSpan>[
                        TextSpan(
                          text: item.id.toString() + ". " + item.title,
                          style: TextStyle(fontSize: 18, color: Colors.red),
                        ),
                        TextSpan(
                          text: '\n' + item.body,
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        TextSpan(
                          text: "\nUser ID:" + item.userId.toString(),
                          style: TextStyle(fontSize: 18),
                        ),
                      ],
                    ),
                  ));
            },
          );
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  }
}
class MyHomePage extends StatefulWidget {
  final String title;
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    context.read<PostCubit>().fetchData();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          actions: <Widget>[
            PopupMenuButton<SortState>(
              icon: Icon(Icons.more_vert),
              itemBuilder: (context) => [
                PopupMenuItem(
                  child: Text('使用 userId 排序'),
                  value: SortState.userId,
                ),
                PopupMenuItem(
                  child: Text('使用 id 排序'),
                  value: SortState.id,
                ),
                PopupMenuItem(
                  child: Text('使用 title 排序'),
                  value: SortState.title,
                ),
                PopupMenuItem(
                  child: Text('使用 body 排序'),
                  value: SortState.body,
                )
              ],
              onSelected: (SortState value) {
                context.read<PostCubit>().sort(value);
              },
            )
          ],
        ),
        body: _MyListView());
  }
}
Bloc 則是把事件分開需要有 Event、Bloc、State 作為事件。
這邊順便介紹一個 Android Studio Bloc 套件
part of 'post_bloc.dart';
abstract class IPostEvent {}
/// cal Api 的事件
class FetchPostData extends IPostEvent {}
/// 排序的事件
class SortPostEvent extends IPostEvent {
  final SortState sortState;
  SortPostEvent({required this.sortState});
}
enum SortState { userID, id, title, body }
class PostBloc extends Bloc<IPostEvent, IPostState> {
  final IPostRepository _repository;
  List<PostModel> postList = [];
  /// 設定初始狀態 super(這裡要放初始狀態)
  PostBloc({required IPostRepository repository}) : _repository = repository,super(PostLoading());
  @override
  Stream<IPostState> mapEventToState(IPostEvent event) async* {
    // 如果是件事 FetchPostData
    if (event is FetchPostData) {yield* _fetchData(event);}
    // 如果是件事 SortPostEvent
    else if(event is SortPostEvent){yield* _sortState(event);}
  }
  Stream<IPostState> _fetchData(FetchPostData event) async* {
    // 請求 API
    postList = await _repository.fetchData();
    // 觸發另一個排序事件 SortPostEvent
    add(SortPostEvent(sortState: SortState.id));
  }
  // 自定義的 method
  Stream<IPostState> _sortState(SortPostEvent event) async* {
    _sort(event.sortState);
    yield PostSuccess(postList);
  }
  // sort method
  Future<void> _sort(SortState sortState)async {
    switch (sortState) {
      case SortState.title:
        postList.sort((a, b) => a.title.compareTo(b.title));
        break;
      case SortState.id:
        postList.sort((a, b) => a.id.compareTo(b.id));
        break;
      case SortState.userID:
        postList.sort((a, b) => a.userId.compareTo(b.userId));
        break;
      case SortState.body:
        postList.sort((a, b) => a.body.compareTo(b.body));
        break;
    }
  }
}
這邊的 State 和 Cubit 一樣。
State 的程式碼 => GITHUB
這邊初始化設定和 Cubit 一樣。
初始化的程式碼 => GITHUB
bloc 是使用 add() 裡面放 event 的 class
/// cubit
 context.read<PostCubit>().sort(value);
/// bloc
 context.read<PostBloc>().add(SortPostEvent(sortState: value));